const _ = require("lodash");
const Joi = require("joi");
class ValidationError extends Error {
  constructor(errors) {
    super(errors.message);
    this.status = errors.status || 400;
    this.errors = errors;
  }
}

class Validation {
  constructor(joi) {
    this.Joi = joi || Joi;
  }

  /**
   * @function validate 中间件验证
   * @param {Object} schema
   * {
   *   body: {},
   *   params: {},
   *   query: {},
   *   status: // 自定义错误状态码
   * }
   */
  validate(schema = {}) {
    const options = _.defaultsDeep(schema, {
      allowUnknown: true,
    });
    return async (ctx, next) => {
      this._validate(ctx, schema, options);
      await next();
    };
  }

  /**
   * @function validateAsync 中间件验证
   * @param {Object} schema
   * {
   *   body: {},
   *   params: {},
   *   query: {},
   *   status: // 自定义错误状态码
   * }
   */
  validateAsync(ctx) {
    return async (schema = {}) => {
      const options = _.defaultsDeep(schema, {
        allowUnknown: true,
      });
      this._validate(ctx, schema, options);
    };
  }

  /**
   * @function _validate 验证
   * @param {Context} ctx
   * @param {Object} schema
   * @param {Object} options
   */
  _validate(ctx, schema, options) {
    const defaultValidateKeys = ["body", "query", "params"];
    const needValidateKeys = _.intersection(
      defaultValidateKeys,
      Object.keys(schema)
    );
    let message;
    needValidateKeys.find((item) => {
      const toValidateObj = item === "body" ? ctx.request.body : ctx[item];
      let _validateSchema;
      if (Array.isArray(schema[item])) {
        const _schema = this.Joi.object(schema[item][0]);
        _validateSchema = this.Joi.array().items(_schema);
      } else {
        _validateSchema = this.Joi.object(schema[item]);
      }

      const result = _validateSchema.validate(toValidateObj, options);
      if (result.error instanceof Error) {
        message = result.error.message;
        return true;
      }
      _.assignIn(toValidateObj, result.value);
      return false;
    });
    if (!!message) {
      const { status } = options;
      throw new ValidationError({ message, status });
    }
    return true;
  }
}

// const defaultValidation = new Validation();
// module.exports = defaultValidation.validate.bind(defaultValidation);
// module.exports.ValidationError = ValidationError;
// module.exports.Validation = Validation;

/**
 * validate 验证插件
 * @param {Applaction} app
 *
 * 使用方法：
 * 1、通过中间件使用，如：
   router.get('*', app.validate({
     body: {
       name: Joi.string().required(),
       password: Joi.string().required(),
     }
   }))
 *
 * 2、在controller中使用
 * await ctx.validateAsync({
      body: {
        name: Joi.string().required(),
        password: Joi.string().required(),
      },
      status: 10001
    });
 */
module.exports = (app) => {
  const defaultValidation = new Validation();

  app.validate = defaultValidation.validate.bind(defaultValidation);
  app.use((ctx, next) => {
    if (!ctx.validateAsync) {
      ctx.validateAsync = defaultValidation.validateAsync(ctx);
    }
    return next();
  });
};
